package com.suxin.netty.remoting;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import lombok.Getter;

import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Tang
 * @classname NettyRemotingClient
 * @description [  ]
 * @date 2022/12/8 23:24
 */
public class NettyRemotingClient extends AbstractNettyRemoting implements RemotingClient {

    private Bootstrap bootstrap;
    private final NioEventLoopGroup eventLoopGroupWork;
    private final DefaultEventExecutorGroup defaultEventExecutorGroup;

    private final NettyClientConfig nettyClientConfig;

    public static void main(String[] args) {
        NettyClientConfig config = new NettyClientConfig();
        NettyRemotingClient client = new NettyRemotingClient(config);
    }

    public NettyRemotingClient(NettyClientConfig clientConfig) {
        this.nettyClientConfig = clientConfig;
        this.eventLoopGroupWork = new NioEventLoopGroup(1, new ThreadFactory() {
            private final AtomicInteger threadIndex = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(String.format("NettyClientSelector_%d", this.threadIndex.incrementAndGet()));
            }
        });
        this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(
                nettyClientConfig.getClientWorkerThreads(),
                new ThreadFactory() {
            private final AtomicInteger threadIndex = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet());
            }
        });
    }

    @Override
    public void start() {
        this.bootstrap = new Bootstrap();
        this.bootstrap.group(this.eventLoopGroupWork)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, false)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
                .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
                .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize())
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast(NettyRemotingClient.this.defaultEventExecutorGroup,
                                        new NettyEncoder(),
                                        new NettyDecoder(),
                                        new IdleStateHandler(5, 5, 10, TimeUnit.SECONDS),
                                        new NettyClientHandler(NettyRemotingClient.this));
                    }
                });
    }

    @Override
    public ChannelWrapper connect(String addr,Integer port) {
        ChannelFuture future = this.bootstrap.connect(new InetSocketAddress(addr, port));
        return new ChannelWrapper(future);
    }

    @Override
    public void shutdown() {
        this.eventLoopGroupWork.shutdownGracefully();
    }

    @Override
    public RemotingCommand invokeSync(RemotingCommand request, long timeoutMillis) {

        return null;
    }

    @Override
    public RemotingCommand invokeAsync(RemotingCommand request, long timeoutMillis, InvokeCallback callback) {
        return null;
    }


    static class ChannelWrapper{

        @Getter
        private final ChannelFuture channelFuture;

        public ChannelWrapper(ChannelFuture channelFuture) {
            this.channelFuture = channelFuture;
        }

        public Channel getChannel() {
            return channelFuture.channel();
        }
    }

}
